home *** CD-ROM | disk | FTP | other *** search
/ PD Collection CD 1 / PD Collection CD 1.iso / textual / gnudiff / C / Diff < prev    next >
Text File  |  1991-10-02  |  21KB  |  803 lines

  1. /* GNU DIFF main routine.
  2.    Copyright (C) 1988, 1989 Free Software Foundation, Inc.
  3.  
  4. This file is part of GNU DIFF.
  5.  
  6. GNU DIFF is free software; you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation; either version 1, or (at your option)
  9. any later version.
  10.  
  11. GNU DIFF is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. GNU General Public License for more details.
  15.  
  16. You should have received a copy of the GNU General Public License
  17. along with GNU DIFF; see the file COPYING.  If not, write to
  18. the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
  19.  
  20. /* GNU DIFF was written by Mike Haertel, David Hayes,
  21.    Richard Stallman and Len Tower.  */
  22.  
  23. #define GDIFF_MAIN
  24. #include "regex.h"
  25. #include "diff.h"
  26. #include "getopt.h"
  27.  
  28. static char *option_list (char **, int);
  29. static int get_filestat (char *, struct file_data *);
  30. static int open (char *);
  31. static void close (int);
  32.  
  33. /* Nonzero for -r: if comparing two directories,
  34.    compare their common subdirectories recursively.  */
  35.  
  36. int recursive;
  37.  
  38. /* For debugging: don't do discard_confusing_lines.  */
  39.  
  40. int no_discards;
  41.  
  42. /* Return a string containing the command options with which diff was invoked.
  43.    Spaces appear between what were separate ARGV-elements.
  44.    There is a space at the beginning but none at the end.
  45.    If there were no options, the result is an empty string.
  46.  
  47.    Arguments: OPTIONVEC, a vector containing separate ARGV-elements, and COUNT,
  48.    the length of that vector.  */
  49.  
  50. static char *option_list (char **optionvec, int count)
  51. {
  52.   int i;
  53.   int length = 0;
  54.   char *result;
  55.  
  56.   for (i = 0; i < count; i++)
  57.     length += strlen (optionvec[i]) + 1;
  58.  
  59.   result = (char *) xmalloc (length + 1);
  60.   result[0] = 0;
  61.  
  62.   for (i = 0; i < count; i++)
  63.     {
  64.       strcat (result, " ");
  65.       strcat (result, optionvec[i]);
  66.     }
  67.  
  68.   return result;
  69. }
  70.  
  71. /* Argument processing */
  72.  
  73. #define OPTIONS "?0123456789abBcC:dD:efF:hHiI:lL:nNpqrsS:tTuvw"
  74.  
  75. /* The numbers 129 and 130 that appear in the fourth element
  76.    for the context and unidiff entries are used as a way of
  77.    telling the big switch in `main' how to process those options.  */
  78.  
  79. static struct option longopts[] =
  80. {
  81.   {"ignore-blank-lines", 0, 0, 'B'},
  82.   {"context", 2, 0, 129},
  83.   {"ifdef", 1, 0, 'D'},
  84.   {"show-function-line", 1, 0, 'F'},
  85.   {"speed-large-files", 0, 0, 'H'},
  86.   {"ignore-matching-lines", 1, 0, 'I'},
  87.   {"file-label", 1, 0, 'L'},
  88.   {"entire-new-files", 0, 0, 'N'},
  89.   {"new-files", 0, 0, 'N'},
  90.   {"starting-file", 1, 0, 'S'},
  91.   {"initial-tab", 0, 0, 'T'},
  92.   {"text", 0, 0, 'a'},
  93.   {"all-text", 0, 0, 'a'},
  94.   {"ascii", 0, 0, 'a'},
  95.   {"ignore-space-change", 0, 0, 'b'},
  96.   {"minimal", 0, 0, 'd'},
  97.   {"ed", 0, 0, 'e'},
  98.   {"reversed-ed", 0, 0, 'f'},
  99.   {"ignore-case", 0, 0, 'i'},
  100.   {"print", 0, 0, 'l'},
  101.   {"rcs", 0, 0, 'n'},
  102.   {"show-c-function", 0, 0, 'p'},
  103.   {"binary", 0, 0, 'q'},
  104.   {"brief", 0, 0, 'q'},
  105.   {"recursive", 0, 0, 'r'},
  106.   {"report-identical-files", 0, 0, 's'},
  107.   {"expand-tabs", 0, 0, 't'},
  108.   {"ignore-all-space", 0, 0, 'w'},
  109.   {"unified", 2, 0, 130},
  110.   {"version", 0, 0, 'v'},
  111.   {0, 0, 0, 0}
  112. };
  113.  
  114. #define USAGE \
  115. "Usage: Diff [options] file1 file2\n" \
  116. "\n" \
  117. "Options:\n" \
  118. "Short   Long                            Description\n" \
  119. "-99                                     Set amount of context to 99\n" \
  120. "-a      +text +all-text +ascii          Treat all files as text\n" \
  121. "-b      +ignore-space-character         Ignore changes in spaces (compare -w)\n" \
  122. "-B      +ignore-blank-lines             Ignore blank lines\n" \
  123. "-C      +context[=lines]                Context diff, with <lines=3> context\n" \
  124. "-c                                      Context diff, with default context\n" \
  125. "-d      +minimal                        Produce a guaranteed minimal diff\n" \
  126. "-D      +ifdef=id                       Mark changes with <#ifdef id>\n" \
  127. "-e      +ed                             Generate an ed script\n" \
  128. "-f      +reversed-ed                    Generate a reversed ed script\n" \
  129. "-F      +show-function-line=regex       Context header matches <regex>\n" \
  130. "-h                                      Split files [not implemented]\n" \
  131. "-H      +speed-large-files              Run faster on large files\n" \
  132. "-i      +ignore-case                    Ignore case when comparing\n" \
  133. "-I      +ignore-matching-lines=regex    Ignore lines which match <regex>\n" \
  134. "-l      +print                          Send output to \"pr\"\n" \
  135. "-L      +file-label=label               Label file with <label>\n" \
  136. "-n      +rcs                            Generate RCS output\n" \
  137. "-N      +new-files +entire-new-files    Treat missing files as empty\n" \
  138. "-p      +show-c-function                Context header is last C function\n" \
  139. "-q      +binary                         Treat all files as binary\n" \
  140. "-r      +recursive                      Recursively check sub-directories\n" \
  141. "-s      +report-identical-files         Report files which match\n" \
  142. "-S      +starting-file=file             Start directory scan at <file>\n" \
  143. "-t      +expand-tabs                    Expand tabs in output\n" \
  144. "-T      +initial-tab                    Precede output lines with TAB\n" \
  145. "-v      +version                        Print the version of diff\n" \
  146. "-u                                      Unified diff, with default context\n" \
  147. "        +unified[=lines]                Unified diff, with <lines=3> context\n" \
  148. "-w      +ignore-all-space               Ignore all spaces (compare -b)\n"
  149.  
  150. /* Main program */
  151.  
  152. int main (int argc, char *argv[])
  153. {
  154.   int val;
  155.   int c;
  156.   int prev = -1;
  157.   int longind;
  158.   extern char *version_string;
  159.  
  160.   program = argv[0];
  161.  
  162.   /* Do our initializations. */
  163.   output_style = OUTPUT_NORMAL;
  164.   always_text_flag = FALSE;
  165.   ignore_space_change_flag = FALSE;
  166.   ignore_all_space_flag = FALSE;
  167.   length_varies = FALSE;
  168.   ignore_case_flag = FALSE;
  169.   ignore_blank_lines_flag = FALSE;
  170.   ignore_regexp = 0;
  171.   function_regexp = 0;
  172.   print_file_same_flag = FALSE;
  173.   entire_new_file_flag = FALSE;
  174.   no_details_flag = FALSE;
  175.   context = -1;
  176.   line_end_char = '\n';
  177.   tab_align_flag = FALSE;
  178.   tab_expand_flag = FALSE;
  179.   recursive = FALSE;
  180.   paginate_flag = FALSE;
  181.   ifdef_string = NULL;
  182.   heuristic = FALSE;
  183.   dir_start_file = NULL;
  184.   msg_chain = NULL;
  185.   msg_chain_end = NULL;
  186.   no_discards = 0;
  187.  
  188.   /* Decode the options.  */
  189.  
  190.   while ((c = getopt_long (argc, argv, OPTIONS, longopts, &longind)) != OPT_END)
  191.     {
  192.       if (c == OPT_LONG)        /* Long option. */
  193.     c = longopts[longind].val;
  194.       switch (c)
  195.     {
  196.       /* All digits combine in decimal to specify the context-size.  */
  197.     case '1':
  198.     case '2':
  199.     case '3':
  200.     case '4':
  201.     case '5':
  202.     case '6':
  203.     case '7':
  204.     case '8':
  205.     case '9':
  206.     case '0':
  207.       if (context == -1)
  208.         context = 0;
  209.       /* If a context length has already been specified,
  210.          more digits allowed only if they follow right after the others.
  211.          Reject two separate runs of digits, or digits after -C.  */
  212.       else if (prev < '0' || prev > '9')
  213.         fatal ("context length specified twice");
  214.  
  215.       context = context * 10 + c - '0';
  216.       break;
  217.  
  218.     case 'a':
  219.       /* Treat all files as text files; never treat as binary.  */
  220.       always_text_flag = 1;
  221.       break;
  222.  
  223.     case 'b':
  224.       /* Ignore changes in amount of whitespace.  */
  225.       ignore_space_change_flag = 1;
  226.       length_varies = 1;
  227.       break;
  228.  
  229.     case 'B':
  230.       /* Ignore changes affecting only blank lines.  */
  231.       ignore_blank_lines_flag = 1;
  232.       break;
  233.  
  234.     case 'C':
  235.     case 129:        /* +context[=lines] */
  236.     case 130:        /* +unified[=lines] */
  237.       if (optarg)
  238.         {
  239.           if (context >= 0)
  240.         fatal ("context length specified twice");
  241.           {
  242.         char *p;
  243.         for (p = optarg; *p; p++)
  244.           if (*p < '0' || *p > '9')
  245.             fatal ("invalid context length argument");
  246.           }
  247.           context = atoi (optarg);
  248.         }
  249.  
  250.       /* Falls through.  */
  251.     case 'c':
  252.       /* Make context-style output.  */
  253.       specify_style (c == 130 ? OUTPUT_UNIFIED : OUTPUT_CONTEXT);
  254.       break;
  255.  
  256.     case 'd':
  257.       /* Don't discard lines.  This makes things slower (sometimes much
  258.          slower) but will find a guaranteed minimal set of changes.  */
  259.       no_discards = 1;
  260.       break;
  261.  
  262.     case 'D':
  263.       /* Make merged #ifdef output.  */
  264.       specify_style (OUTPUT_IFDEF);
  265.       ifdef_string = optarg;
  266.       break;
  267.  
  268.     case 'e':
  269.       /* Make output that is a valid `ed' script.  */
  270.       specify_style (OUTPUT_ED);
  271.       break;
  272.  
  273.     case 'f':
  274.       /* Make output that looks vaguely like an `ed' script
  275.          but has changes in the order they appear in the file.  */
  276.       specify_style (OUTPUT_FORWARD_ED);
  277.       break;
  278.  
  279.     case 'F':
  280.       /* Show, for each set of changes, the previous line that
  281.          matches the specified regexp.  Currently affects only
  282.          context-style output.  */
  283.       function_regexp = optarg;
  284.       break;
  285.  
  286.     case 'h':
  287.       /* Split the files into chunks of around 1500 lines
  288.          for faster processing.  Usually does not change the result.
  289.  
  290.          This currently has no effect.  */
  291.       break;
  292.  
  293.     case 'H':
  294.       /* Turn on heuristics that speed processing of large files
  295.          with a small density of changes.  */
  296.       heuristic = 1;
  297.       break;
  298.  
  299.     case 'i':
  300.       /* Ignore changes in case.  */
  301.       ignore_case_flag = 1;
  302.       break;
  303.  
  304.     case 'I':
  305.       /* Ignore changes affecting only lines that match the
  306.          specified regexp.  */
  307.       ignore_regexp = optarg;
  308.       break;
  309.  
  310.     case 'l':
  311.       /* Pass the output through `pr' to paginate it.  */
  312.       paginate_flag = 1;
  313.       break;
  314.  
  315.     case 'L':
  316.       /* Specify file labels for `-c' output headers.  */
  317.       if (!file_label[0])
  318.         file_label[0] = optarg;
  319.       else if (!file_label[1])
  320.         file_label[1] = optarg;
  321.       else
  322.         fatal ("too many file label options");
  323.       break;
  324.  
  325.     case 'n':
  326.       /* Output RCS-style diffs, like `-f' except that each command
  327.          specifies the number of lines affected.  */
  328.       specify_style (OUTPUT_RCS);
  329.       break;
  330.  
  331.     case 'N':
  332.       /* When comparing directories, if a file appears only in one
  333.          directory, treat it as present but empty in the other.  */
  334.       entire_new_file_flag = 1;
  335.       break;
  336.  
  337.     case 'p':
  338.       /* Make context-style output and show name of last C function.  */
  339.       specify_style (OUTPUT_CONTEXT);
  340.       function_regexp = "^[_a-zA-Z].*\\(";
  341.       break;
  342.  
  343.     case 'q':
  344.       no_details_flag = 1;
  345.       break;
  346.  
  347.     case 'r':
  348.       /* When comparing directories, 
  349.          recursively compare any subdirectories found.  */
  350.       recursive = 1;
  351.       break;
  352.  
  353.     case 's':
  354.       /* Print a message if the files are the same.  */
  355.       print_file_same_flag = 1;
  356.       break;
  357.  
  358.     case 'S':
  359.       /* When comparing directories, start with the specified
  360.          file name.  This is used for resuming an aborted comparison.  */
  361.       dir_start_file = optarg;
  362.       break;
  363.  
  364.     case 't':
  365.       /* Expand tabs to spaces in the output so that it preserves
  366.          the alignment of the input files.  */
  367.       tab_expand_flag = 1;
  368.       break;
  369.  
  370.     case 'T':
  371.       /* Use a tab in the output, rather than a space, before the
  372.          text of an input line, so as to keep the proper alignment
  373.          in the input line without changing the characters in it.  */
  374.       tab_align_flag = 1;
  375.       break;
  376.  
  377.     case 'u':
  378.       /* Output the context diff in unidiff format.  */
  379.       specify_style (OUTPUT_UNIFIED);
  380.       break;
  381.  
  382.     case 'v':
  383.       fprintf (stderr, "GNU diff version %s\n", version_string);
  384.       exit (0);
  385.  
  386.     case 'w':
  387.       /* Ignore horizontal whitespace when comparing lines.  */
  388.       ignore_all_space_flag = 1;
  389.       length_varies = 1;
  390.       break;
  391.  
  392.     case '?':
  393.       fprintf(stderr, "%s", USAGE);
  394.       exit (0);
  395.  
  396.     case OPT_ERR:
  397.     default:
  398.       exit (2);
  399.     }
  400.       prev = c;
  401.     }
  402.  
  403.   if (optind != argc - 2)
  404.     {
  405.       fprintf(stderr, "Diff: Requires 2 arguments\n");
  406.       exit (2);
  407.     }
  408.  
  409.   if (ignore_regexp)
  410.     {
  411.       char *val;
  412.       bzero (&ignore_regexp_compiled, sizeof ignore_regexp_compiled);
  413.       val = re_compile_pattern (ignore_regexp, strlen (ignore_regexp),
  414.                 &ignore_regexp_compiled);
  415.       if (val != 0)
  416.     error ("%s: %s", ignore_regexp, val);
  417.       ignore_regexp_compiled.fastmap = (char *) xmalloc (256);
  418.     }
  419.  
  420.   if (function_regexp)
  421.     {
  422.       char *val;
  423.       bzero (&function_regexp_compiled, sizeof function_regexp_compiled);
  424.       val = re_compile_pattern (function_regexp, strlen (function_regexp),
  425.                 &function_regexp_compiled);
  426.       if (val != 0)
  427.     error ("%s: %s", function_regexp, val);
  428.       function_regexp_compiled.fastmap = (char *) xmalloc (256);
  429.     }
  430.  
  431.   if (output_style != OUTPUT_CONTEXT && output_style != OUTPUT_UNIFIED)
  432.     context = 0;
  433.   else if (context == -1)
  434.     /* Default amount of context for -c.  */
  435.     context = 3;
  436.  
  437.   switch_string = option_list (argv + 1, optind - 1);
  438.  
  439.   val = compare_files (0, argv[optind], 0, argv[optind + 1], 0);
  440.  
  441.   /* Print any messages that were saved up for last.  */
  442.   print_message_queue ();
  443.  
  444.   if (ferror (stdout) || fclose (stdout) != 0)
  445.     fatal ("write error");
  446.   exit (val);
  447. }
  448.  
  449. void specify_style (enum output_style style)
  450. {
  451.   if (output_style != OUTPUT_NORMAL
  452.       && output_style != style)
  453.     error ("conflicting specifications of output style");
  454.   output_style = style;
  455. }
  456.  
  457. /* Compare two files (or dirs) with specified names
  458.    DIR0/NAME0 and DIR1/NAME1, at level DEPTH in directory recursion.
  459.    (if DIR0 is 0, then the name is just NAME0, etc.)
  460.    This is self-contained; it opens the files and closes them.
  461.  
  462.    Value is 0 if files are identical, 1 if different,
  463.    2 if there is a problem opening them.  */
  464.  
  465. int compare_files (char *dir0, char *name0, char *dir1, char *name1, int depth)
  466. {
  467.   static char Standard_Input[] = "Standard Input";
  468.   struct file_data inf[2];
  469.   register int i;
  470.   int val;
  471.   int errorcount = 0;
  472.  
  473.  
  474.   /* If this is directory comparison, perhaps we have a file
  475.      that exists only in one of the directories.
  476.      If so, just print a message to that effect.  */
  477.  
  478.   if (! entire_new_file_flag && (name0 == 0 || name1 == 0))
  479.     {
  480.       char *name = name0 == 0 ? name1 : name0;
  481.       char *dir = name0 == 0 ? dir1 : dir0;
  482.       message ("Only in %s: %s\n", dir, name);
  483.       /* Return 1 so that diff_dirs will return 1 ("some files differ").  */
  484.       return 1;
  485.     }
  486.  
  487.   /* Mark any nonexistent file with -1 in the desc field.  */
  488.  
  489.   inf[0].desc = name0 == 0 ? -1 : 0;
  490.   inf[1].desc = name1 == 0 ? -1 : 0;
  491.  
  492.   /* Now record the full name of each file, including nonexistent ones.  */
  493.  
  494.   if (name0 == 0)
  495.     name0 = name1;
  496.   if (name1 == 0)
  497.     name1 = name0;
  498.  
  499.   inf[0].name = dir0 == 0 ? name0 : concat (dir0, ".", name0);
  500.   inf[1].name = dir1 == 0 ? name1 : concat (dir1, ".", name1);
  501.  
  502.   /* Stat the files.  Record whether they are directories.
  503.      Record in stat_result whether stat fails.  */
  504.  
  505.   for (i = 0; i <= 1; i++)
  506.     {
  507.       inf[i].length = 0;
  508.       inf[i].timestamp[0] = 0;
  509.       inf[i].dir_p = 0;
  510.  
  511.       if (inf[i].desc != -1)
  512.     {
  513.       if (get_filestat(inf[i].name, &inf[i]))
  514.           errorcount = 1;
  515.     }
  516.  
  517.       if (inf[i].desc == 0)
  518.         inf[i].name = Standard_Input;
  519.     }
  520.  
  521.   if (name0 == 0)
  522.     inf[0].dir_p = inf[1].dir_p;
  523.   if (name1 == 0)
  524.     inf[1].dir_p = inf[0].dir_p;
  525.  
  526.   if (errorcount)
  527.     {
  528.       /* If either file should exist but fails to be opened, return 2.  */
  529.       val = 2;
  530.     }
  531.   else if (inf[0].dir_p && inf[1].dir_p)
  532.     {
  533.       if (output_style == OUTPUT_IFDEF)
  534.     fatal ("-D option not supported with directories");
  535.  
  536.       /* If both are directories, compare the files in them.  */
  537.  
  538.       if (depth > 0 && !recursive)
  539.     {
  540.       /* But don't compare dir contents one level down
  541.          unless -r was specified.  */
  542.       message ("Common subdirectories: %s and %s\n",
  543.            inf[0].name, inf[1].name);
  544.       val = 0;
  545.     }
  546.       else
  547.     {
  548.       val = diff_dirs (inf[0].name, inf[1].name, 
  549.                compare_files, depth, 0, 0);
  550.     }
  551.  
  552.     }
  553.   else if (depth == 0 && (inf[0].dir_p || inf[1].dir_p))
  554.     {
  555.  
  556.       /* If only one is a directory, and it was specified in the command line,
  557.      use the file in that dir whose basename matches the other file.  */
  558.  
  559.       int dir_arg = (inf[0].dir_p ? 0 : 1);
  560.       int fnm_arg = (inf[0].dir_p ? 1 : 0);
  561.       char *p;
  562.       char *filename;
  563.  
  564.       if (inf[fnm_arg].name == Standard_Input)
  565.     fatal ("can't compare - to a directory");
  566.  
  567.       p = rindex (inf[fnm_arg].name, '.');
  568.       if (p == 0)
  569.         p = rindex (inf[fnm_arg].name, ':');
  570.  
  571.       filename = concat (inf[dir_arg].name, ".", (p ? p+1 : inf[fnm_arg].name));
  572.  
  573.       if (get_filestat(filename,&inf[dir_arg]))
  574.     {
  575.       val = 2;
  576.     }
  577.       else
  578.     {
  579.       /* JF: patch from the net to check and make sure we can really free
  580.          this.  If it's from argv[], freeing it is a *really* bad idea */
  581.       if (0 != (dir_arg ? dir1 : dir0))
  582.         free (inf[dir_arg].name);
  583.       inf[dir_arg].name = filename;
  584.  
  585.       if (inf[dir_arg].dir_p)
  586.         {
  587.           error ("%s is a directory but %s is not",
  588.              inf[dir_arg].name, inf[fnm_arg].name);
  589.           val = 1;
  590.         }
  591.       else
  592.         val = diff_2_files (inf, depth);
  593.     }
  594.  
  595.     }
  596.   else if (depth > 0 && (inf[0].dir_p || inf[1].dir_p))
  597.     {
  598.       /* Perhaps we have a subdirectory that exists only in one directory.
  599.      If so, just print a message to that effect.  */
  600.  
  601.       if (inf[0].desc == -1 || inf[1].desc == -1)
  602.     {
  603.       if (entire_new_file_flag && recursive)
  604.         val = diff_dirs (inf[0].name, inf[1].name, compare_files, depth,
  605.                  inf[0].desc == -1, inf[1].desc == -1);
  606.       else
  607.         {
  608.           char *dir = (inf[0].desc == -1) ? dir1 : dir0;
  609.           message ("Only in %s: %s\n", dir, name0);
  610.           val = 1;
  611.         }
  612.     }
  613.       else
  614.     {
  615.       /* We have a subdirectory in one directory
  616.          and a file in the other.  */
  617.  
  618.       if (inf[0].dir_p)
  619.         message ("%s is a directory but %s is not\n",
  620.              inf[0].name, inf[1].name);
  621.       else
  622.         message ("%s is a directory but %s is not\n",
  623.              inf[1].name, inf[0].name);
  624.       /* This is a difference.  */
  625.       val = 1;
  626.     }
  627.     }
  628.   else
  629.     {
  630.       /* Both exist and both are ordinary files.  */
  631.       val = diff_2_files (inf, depth);
  632.     }
  633.  
  634.   /* Now the comparison has been done, if no error prevented it,
  635.      and VAL is the value this function will return.  */
  636.  
  637.   if (inf[0].desc > 0)
  638.     close (inf[0].desc);
  639.   if (inf[1].desc > 0)
  640.     close (inf[1].desc);
  641.  
  642.   if (val == 0 && !inf[0].dir_p)
  643.     {
  644.       if (print_file_same_flag)
  645.     message ("Files %s and %s are identical\n",
  646.          inf[0].name, inf[1].name);
  647.     }
  648.   else
  649.     fflush (stdout);
  650.  
  651.   if (dir0 != 0)
  652.     free (inf[0].name);
  653.   if (dir1 != 0)
  654.     free (inf[1].name);
  655.  
  656.   return val;
  657. }
  658.  
  659. static int get_filestat (char *filename, struct file_data *inf)
  660. {
  661.   int res;
  662.   _kernel_osfile_block blk;
  663.   _kernel_swi_regs regs;
  664.   char time_blk[5];
  665.  
  666.   /* Standard input - return a desc of 0 */
  667.   if (strcmp(filename, "-") == 0)
  668.     {
  669.       inf->desc = 0;
  670.       return 0;
  671.     }
  672.  
  673.   /* Try to get the file catalogue information */
  674.   res = _kernel_osfile(5, filename, &blk);
  675.  
  676.   if (res == _kernel_ERROR)
  677.     {
  678.       _kernel_oserror *err = _kernel_last_oserror();
  679.       fprintf(stderr,"%s: Cannot read %s (Error %d - %s)\n",
  680.           program, filename, err->errnum, err->errmess);
  681.       return 1;
  682.     }
  683.   else if (res == 0)
  684.     {
  685.       fprintf(stderr,"%s: Cannot read %s\n", program, filename);
  686.       return 1;
  687.     }
  688.  
  689.   /* Is the file a directory? */
  690.   inf->dir_p = (res == 2);
  691.  
  692.   /* For a directory, simply set the file descriptor to -2 and return */
  693.   if (res == 2)
  694.     {
  695.       inf->desc = -2;
  696.       return 0;
  697.     }
  698.  
  699.   /* Now set up the inf structure for a normal file */
  700.   inf->length = blk.start;
  701.   inf->desc = open (filename);
  702.  
  703.   if (inf->desc == -1)
  704.     return 1;
  705.  
  706.   /* Finally, the timestamp. We leave it until last, as it's hard */
  707.   if ((blk.load & 0xFFF00000) != 0xFFF00000)
  708.     {
  709.       inf->timestamp[0] = 0;
  710.       return 0;
  711.     }
  712.  
  713.   time_blk[0] = blk.exec & 0xFF;
  714.   time_blk[1] = (blk.exec >> 8) & 0xFF;
  715.   time_blk[2] = (blk.exec >> 16) & 0xFF;
  716.   time_blk[3] = (blk.exec >> 24) & 0xFF;
  717.   time_blk[4] = blk.load & 0xFF;
  718.  
  719.   regs.r[0] = (int)time_blk;
  720.   regs.r[1] = (int)inf->timestamp;
  721.   regs.r[2] = TIMELEN;
  722.   regs.r[3] = (int)TIMEFORM;
  723.  
  724.   /* For errors, simply clear the timestamp */
  725.   if (_kernel_swi(OS_ConvertDateAndTime,®s,®s))
  726.     inf->timestamp[0] = 0;
  727.  
  728.   return 0;
  729. }
  730.  
  731. /* Open and close a file descriptor. Register an atexit() handler to
  732.  * tidy up files left open on exit.
  733.  */
  734.  
  735. static int open_fds[_SYS_OPEN];
  736. static int num_open = 0;
  737. static int registered = 0;
  738. static void close_fds (void);
  739.  
  740. static int open (char *filename)
  741. {
  742.   int fd;
  743.  
  744.   /* Register the close routine, if necessary */
  745.   if (!registered)
  746.     atexit (close_fds);
  747.  
  748.   /* Open the file for reading */
  749.   fd = _kernel_osfind (0x4C, filename);
  750.  
  751.   /* Trap any OS errors, and return -1 */
  752.   if (fd == _kernel_ERROR)
  753.     {
  754.       _kernel_oserror *err = _kernel_last_oserror();
  755.       fprintf(stderr,"%s: Cannot open %s (Error %d - %s)\n",
  756.           program, filename, err->errnum, err->errmess);
  757.       return -1;
  758.     }
  759.   else if (fd == 0)
  760.     {
  761.       fprintf(stderr,"%s: Cannot open %s\n", program, filename);
  762.       return -1;
  763.     }
  764.  
  765.   /* Add the file to the list of open files */
  766.   if (num_open < _SYS_OPEN)
  767.     open_fds[num_open++] = fd;
  768.  
  769.   return fd;
  770. }
  771.  
  772. static void close (int fd)
  773. {
  774.   int i;
  775.  
  776.   /* Is this fd in the open list? */
  777.   for (i = 0; i < num_open; ++i)
  778.     {
  779.       if (open_fds[i] == fd)
  780.     break;
  781.     }
  782.  
  783.   /* If so, remove it */
  784.   if (i < num_open)
  785.     {
  786.       --num_open;
  787.       open_fds[i] = open_fds[num_open];
  788.       open_fds[num_open] = 0;
  789.     }
  790.  
  791.   /* Close the file anyway */
  792.   _kernel_osfind (0, (char *)fd);
  793. }
  794.  
  795. static void close_fds (void)
  796. {
  797.   int i;
  798.  
  799.   /* Close all the files registered as open */
  800.   for (i = 0; i < num_open; ++i)
  801.     _kernel_osfind (0, (char *)open_fds[i]);
  802. }
  803.